﻿using System;
using System.Collections.Specialized;
using System.Linq;
using System.Web.Fakes;
using System.Web.Http.Controllers;
using System.Web.Mvc;
using Castle.Core;
using Castle.Core.Internal;
using Castle.MicroKernel;
using Castle.Windsor;
using Castle.Windsor.Diagnostics;
using Microsoft.QualityTools.Testing.Fakes;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using User.EF.Model;
using User.Web.UI.Controllers;
using User.Web.UI.Helper.CastleWindsor;

namespace User.Web.UI.Tests.Unit.CommonTests
{
    /// <summary>
    /// Testen unseres Castle Windsor Installers bzw. ob unsere Interfaces zu den richtigen Objekten aufgelöst werden.
    /// http://docs.castleproject.org/Windsor.Windsor-tutorial-part-three-a-testing-your-first-installer.ashx
    /// </summary>
    [TestClass]
    public class CastleWindsorTest
    {
        #region Properties
        private IWindsorContainer ContainerWeb { get; set; }
        #endregion

        #region Initialize
        [TestInitialize]
        public void Initialize()
        {
            using (ShimsContext.Create())
            {
                //Da wir für den Installer auch den HttpContext benötigen um eine Valides Model zu erhalten, wird hier der ShimContext unserer Webseite verwendet.
                var context = new ShimHttpContext();
                ShimHttpContext.CurrentGet = () => { return context; };
                Mock<MainModelContainer> mock = new Mock<MainModelContainer>();
                ShimHttpContext.AllInstances.ItemsGet = (p) => { return new ListDictionary() { { "EFDataModelHttpContextName", mock.Object }}; };

                //Einen neuen Container erstellen für unseren WebInstaller
                ContainerWeb = new WindsorContainer().Install(new WebInstaller(string.Empty));
            }
        }
        #endregion

        #region Castle Windsor Tests
        /// <summary>
        /// Prüft ob es Komponenten gibt die nicht richtig konfiguriert/aufgelöst werden konnten von Castle Windsor
        /// </summary>
        [TestMethod]
        public void CheckFor_PotentiallyMisconfiguredComponents()
        {
            var host = (IDiagnosticsHost)ContainerWeb.Kernel.GetSubSystem(SubSystemConstants.DiagnosticsKey);
            var diagnostics = host.GetDiagnostic<IPotentiallyMisconfiguredComponentsDiagnostic>();

            var handlers = diagnostics.Inspect();

            //Assert
            //ACHTUNG fängt irgendwie noch nicht alle Missconfigured Fälle ab, aber ein paar zumindest.
            Assert.IsTrue(!handlers.Any());
        }

        /// <summary>
        /// Verify that indeed all types implementing IController from our application assembly are registered by the ControllersInstaller
        /// </summary>
        [TestMethod]
        public void AllControllers_AreRegistered()
        {
            // Is<TType> is an helper, extension method from Windsor in the Castle.Core.Internal namespace
            // which behaves like 'is' keyword in C# but at a Type, not instance level
            var allControllers = GetPublicClassesFromApplicationAssembly(c => c.Is<IController>());
            var registeredControllers = GetImplementationTypesFor(typeof(IController), ContainerWeb);

            //Assert
            Assert.IsTrue(allControllers.SequenceEqual(registeredControllers));
        }

        /// <summary>
        /// Prüfen das nur Wirklich auch Controller auf den Suffix "Controller" enden.
        /// </summary>
        [TestMethod]
        public void AllAndOnlyControllers_HaveControllersSuffix()
        {
            var allControllers = GetPublicClassesFromApplicationAssembly(c => c.Name.EndsWith("Controller"));
           
            //Die controller für die API und den Standard Controller ermitteln
            var registeredControllers = GetImplementationTypesFor(typeof(IController), ContainerWeb);
            var regsiteredApiController = GetImplementationTypesFor(typeof(IHttpController), ContainerWeb);
            //Beide Listen mit API und Standard "vereinen"
            var all = registeredControllers.ToList();
            all.AddRange(regsiteredApiController);

            //Assert - Prüfen ob die Liste die gleichen Einträge enthalten und dann pb die Anzahl mit der Gesamtliste übereinstimmt.
            Assert.IsTrue(allControllers.ToList().Intersect(all).Count() == allControllers.Count());
        }

        [TestMethod]
        public void AllControllers_ExposeThemselvesAsService()
        {
            var controllersWithWrongName = GetHandlersFor(typeof(IController), ContainerWeb)
                .Where(controller => controller.ComponentModel.Services.Single() != controller.ComponentModel.Implementation)
                .ToArray();

            //Assert
            Assert.AreEqual(controllersWithWrongName.Count(), 0);
        }

        /// <summary>
        /// Prüfen ob auch alle Castle Eingebundenen Komponenenten auf PerWebRequest eingestellt wurden.
        /// </summary>
        [TestMethod]
        public void AllHandlersLifeStyle_IsPerWebrequest()
        {
            var controllersWithWrongLifeStyle = GetAllHandlers(ContainerWeb)
                .Where(controller => controller.ComponentModel.LifestyleType != LifestyleType.PerWebRequest)
                .ToArray();

            //Assert
            Assert.AreEqual(controllersWithWrongLifeStyle.Count(), 0);
        }

        /// <summary>
        /// Prüfen ob auch alle Controller vom Livestyle PerWebRequest sind.
        /// </summary>
        [TestMethod]
        public void AllControllers_AreTransient()
        {
            var allControllers = GetPublicClassesFromApplicationAssembly(c => c.Is<IController>());
            var nonTransientControllers = GetHandlersFor(typeof(IController), ContainerWeb)
                .Where(controller => controller.ComponentModel.LifestyleType == LifestyleType.PerWebRequest)
                .ToArray();

            //Assert
            Assert.AreEqual(nonTransientControllers.Count(), allControllers.Count());
        }
        #endregion

        #region Helper Methods for Castle Windsor
        private IHandler[] GetAllHandlers(IWindsorContainer container)
        {
            return GetHandlersFor(typeof(object), container);
        }

        private IHandler[] GetHandlersFor(Type type, IWindsorContainer container)
        {
            return container.Kernel.GetAssignableHandlers(type);
        }

        private Type[] GetImplementationTypesFor(Type type, IWindsorContainer container)
        {
            return GetHandlersFor(type, container)
                .Select(h => h.ComponentModel.Implementation)
                .OrderBy(t => t.Name)
                .ToArray();
        }

        private Type[] GetPublicClassesFromApplicationAssembly(Predicate<Type> where)
        {
            return typeof(HomeController).Assembly.GetExportedTypes()
                .Where(t => t.IsClass)
                .Where(t => t.IsAbstract == false)
                .Where(where.Invoke)
                .OrderBy(t => t.Name)
                .ToArray();
        }
        #endregion
    }
}
